from .gl_tools import *
from .controls import *

import sdl2
import sdl2.ext
import zengl
import numpy as np
from pyrr import Matrix44, Quaternion, Vector3
import ctypes


class Window:
    def __init__(self, size=(800, 600)):
        sx, sy = self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        self.camera = Camera(U3(0, 0, 0), xy=sx/sy)   # 默认摄像机
        self.draw3d_list = []

        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []
        self.wheel_callback = []
        self.update_callback = []


        # ~ sdl2.SDL_SetHint(sdl2.SDL_HINT_OPENGL_ES_DRIVER, b"0")
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MAJOR_VERSION, 4)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MINOR_VERSION, 3)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_PROFILE_MASK, sdl2.SDL_GL_CONTEXT_PROFILE_CORE)

        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DOUBLEBUFFER, 1)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DEPTH_SIZE, 24)
        err = sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
        if err != 0:
            print(sdl2.SDL_GetError())

        self.window = window = sdl2.SDL_CreateWindow(b"sketchz",
            sdl2.SDL_WINDOWPOS_UNDEFINED,
            sdl2.SDL_WINDOWPOS_UNDEFINED, sx, sy,
            sdl2.SDL_WINDOW_OPENGL)

        if not window:
            print(sdl2.SDL_GetError())
            exit()

        self._context = sdl2.SDL_GL_CreateContext(window)
        self.ctx = zengl.context()

        self.img = self.ctx.image((sx, sy), "rgba32float")
        self.imgh = self.ctx.image((sx, sy), "rgba32float")     # 很好，同时输出法线和未正规化的深度
        self.imgs = self.ctx.image((sx, sy), "depth32float")


    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == sdl2.SDL_MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e.motion)
        elif e.type == sdl2.SDL_KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEWHEEL :
            for callback in self.wheel_callback:
                r = callback(self, e.wheel)
                if r == True:
                    break


    def render(self):
        self.ctx.new_frame()

        self.img.clear()
        self.imgs.clear()
        self.imgh.clear()

        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光

        self.img.blit()
        # ~ self.imgh.blit()
        self.ctx.end_frame()

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt)

    def mainloop(self):
        running = True
        e = sdl2.SDL_Event()
        while running:
            while sdl2.SDL_PollEvent(ctypes.byref(e)) != 0:
                if e.type == sdl2.SDL_QUIT:
                    running = False
                self.dispatch(e)

            self.update(0.02)
            self.render()

            sdl2.SDL_GL_SwapWindow(self.window)
            sdl2.SDL_Delay(20)


        sdl2.SDL_DestroyWindow(self.window)
        sdl2.SDL_Quit()


class WindowCai:
    def __init__(self, size=(800, 600)):
        sx, sy = self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        self.camera = Camera(U3(0, 0, 0), xy=sx/sy)   # 默认摄像机
        self.draw3d_list = []

        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []
        self.wheel_callback = []
        self.update_callback = []


        # ~ sdl2.SDL_SetHint(sdl2.SDL_HINT_OPENGL_ES_DRIVER, b"0")
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MAJOR_VERSION, 4)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MINOR_VERSION, 3)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_PROFILE_MASK, sdl2.SDL_GL_CONTEXT_PROFILE_CORE)

        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DOUBLEBUFFER, 1)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DEPTH_SIZE, 24)
        err = sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
        if err != 0:
            print(sdl2.SDL_GetError())

        self.window = window = sdl2.SDL_CreateWindow(b"sketchz",
            sdl2.SDL_WINDOWPOS_UNDEFINED,
            sdl2.SDL_WINDOWPOS_UNDEFINED, sx, sy,
            sdl2.SDL_WINDOW_OPENGL)

        if not window:
            print(sdl2.SDL_GetError())
            exit()

        self._context = sdl2.SDL_GL_CreateContext(window)
        self.ctx = zengl.context()

        self.img = self.ctx.image((sx, sy), "rgba32float")
        self.img.clear_value = (1, 1, 1, 1)
        self.imgh = self.ctx.image((sx, sy), "rgba32float")     # 很好，同时输出法线和未正规化的深度
        self.imgs = self.ctx.image((sx, sy), "depth32float")


        self.render2d = Render2d()

        #self.temp = load_texture(self.ctx, "cmyk.png")     # 测试

        self.render2d.u(self)

        import cairo
        self.cairo = cairo

        self.cs = self.cairo.ImageSurface(self.cairo.FORMAT_ARGB32, sx, sy)
        self.cctx = self.cairo.Context(self.cs)
        self.cctx.save()



    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == sdl2.SDL_MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e.motion)
        elif e.type == sdl2.SDL_KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEWHEEL :
            for callback in self.wheel_callback:
                r = callback(self, e.wheel)
                if r == True:
                    break


    def render(self):
        self.ctx.new_frame()

        self.img.clear()
        self.imgs.clear()
        self.imgh.clear()

        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光


        # ~ self.imgh.blit()



        """
        cctx.move_to(0, 0)
        cctx.line_to(200, 400)
        cctx.line_to(600, 200)
        cctx.set_line_width(3)
        r, g, b, a= (0, 255, 0, 255)
        cctx.set_source_rgba(b / 255, g / 255, r / 255, a / 255)
        cctx.stroke()

        from math import pi


        cctx.arc(500, 400, 200, pi / 2, 0)
        cctx.close_path()

        cctx.fill()
        # ~ cctx.stroke()
        """

        cctx = self.cctx
        cctx.restore()
        cctx.save()

        cctx.rectangle(100, 100, 300, 300)
        cctx.rectangle(200, 200, 100, 100)
        r, g, b, a= (128, 128, 255, 255)
        cctx.set_source_rgba(b / 255, g / 255, r / 255, a / 255)
        cctx.set_fill_rule(self.cairo.FILL_RULE_EVEN_ODD)
        cctx.fill()
        # ~ cctx.new_path()

        cctx.rectangle(350, 100, 300, 300)
        cctx.rectangle(450, 200, 100, 100)
        # ~ r, g, b, a= (255, 128, 255, 255)
        # ~ cctx.set_source_rgba(b / 255, g / 255, r / 255, a / 255)

        lg = self.cairo.LinearGradient(350, 100, 300, 300)
        r, g, b, a = (255, 0, 0, 255)
        lg.add_color_stop_rgba(0, b / 255, g / 255, r / 255, a / 255)
        r, g, b, a = (0, 255, 0, 255)
        lg.add_color_stop_rgba(1, b / 255, g / 255, r / 255, a / 255)
        cctx.set_source(lg)
        cctx.fill()
        '''
        pattern = self.cairo.MeshPattern()
        pattern.begin_patch()
        # ~ pattern.move_to(0, 0)
        pattern.move_to(350, 100)
        pattern.curve_to(30, -30, 60, 30, 100, 0)
        pattern.curve_to(60, 30, 130, 60, 100, 100)
        pattern.curve_to(60, 70, 30, 130, 0, 100)
        pattern.curve_to(30, 70, -30, 30, 0, 0)
        pattern.set_corner_color_rgba(0, 1, 0, 0, 1)
        pattern.set_corner_color_rgba(1, 0, 1, 0, 1)
        pattern.set_corner_color_rgba(2, 0, 0, 1, 1)
        pattern.set_corner_color_rgba(3, 1, 1, 0, 1)
        pattern.end_patch()
        pattern.set_extend(self.cairo.EXTEND_REPEAT)
        cctx.set_source(pattern)
        '''



        buf = self.cs.get_data()     # 这是一个memoryview
        # ~ self.render2d.texture.write(buf)
        #cs.finish() # 此时内存被释放，无法再获取



        self.render2d.draw()

        self.img.blit()

        self.ctx.end_frame()

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt)

    def mainloop(self):
        running = True
        e = sdl2.SDL_Event()
        while running:
            while sdl2.SDL_PollEvent(ctypes.byref(e)) != 0:
                if e.type == sdl2.SDL_QUIT:
                    running = False
                self.dispatch(e)

            self.update(0.02)
            self.render()

            sdl2.SDL_GL_SwapWindow(self.window)
            sdl2.SDL_Delay(20)


        sdl2.SDL_DestroyWindow(self.window)
        sdl2.SDL_Quit()



class WindowSur:
    def __init__(self, size=(800, 600)):
        sx, sy = self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        self.camera = Camera(U3(0, 0, 0), xy=sx/sy)   # 默认摄像机
        self.draw3d_list = []

        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []
        self.wheel_callback = []
        self.update_callback = []


        # ~ sdl2.SDL_SetHint(sdl2.SDL_HINT_OPENGL_ES_DRIVER, b"0")
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MAJOR_VERSION, 4)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MINOR_VERSION, 3)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_PROFILE_MASK, sdl2.SDL_GL_CONTEXT_PROFILE_CORE)

        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DOUBLEBUFFER, 1)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DEPTH_SIZE, 24)
        err = sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
        if err != 0:
            print(sdl2.SDL_GetError())

        self.window = window = sdl2.SDL_CreateWindow(b"sketchz",
            sdl2.SDL_WINDOWPOS_UNDEFINED,
            sdl2.SDL_WINDOWPOS_UNDEFINED, sx, sy,
            sdl2.SDL_WINDOW_OPENGL)

        if not window:
            print(sdl2.SDL_GetError())
            exit()

        self._context = sdl2.SDL_GL_CreateContext(window)
        self.ctx = zengl.context()

        self.img = self.ctx.image((sx, sy), "rgba32float")
        self.img.clear_value = (1, 1, 1, 1)
        self.imgh = self.ctx.image((sx, sy), "rgba32float")     # 很好，同时输出法线和未正规化的深度
        self.imgs = self.ctx.image((sx, sy), "depth32float")


        self.screen = sdl2.ext.surface._create_surface(self.size).contents

        self.render2d = Render2d()

        self.render2d.u(self, sdlsurf=True)





    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == sdl2.SDL_MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e.motion)
        elif e.type == sdl2.SDL_KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEWHEEL :
            for callback in self.wheel_callback:
                r = callback(self, e.wheel)
                if r == True:
                    break


    def render(self):
        self.ctx.new_frame()

        self.img.clear()
        self.imgs.clear()
        self.imgh.clear()

        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光



        self.render2d.draw()

        self.img.blit()

        self.ctx.end_frame()

    def flush_screen(self):
        to_image(self.screen, self.render2d.texture)

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt)

    def mainloop(self):
        running = True
        e = sdl2.SDL_Event()
        while running:
            while sdl2.SDL_PollEvent(ctypes.byref(e)) != 0:
                if e.type == sdl2.SDL_QUIT:
                    running = False
                self.dispatch(e)

            self.update(0.02)
            self.render()

            sdl2.SDL_GL_SwapWindow(self.window)
            sdl2.SDL_Delay(20)


        sdl2.SDL_DestroyWindow(self.window)
        sdl2.SDL_Quit()



class WindowAfterCai:
    def __init__(self, size=(800, 600)):
        sx, sy = self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        self.camera = Camera(U3(0, 0, 0), xy=sx/sy)   # 默认摄像机
        self.draw3d_list = []

        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []
        self.wheel_callback = []
        self.update_callback = []


        # ~ sdl2.SDL_SetHint(sdl2.SDL_HINT_OPENGL_ES_DRIVER, b"0")
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MAJOR_VERSION, 4)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MINOR_VERSION, 3)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_PROFILE_MASK, sdl2.SDL_GL_CONTEXT_PROFILE_CORE)

        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DOUBLEBUFFER, 1)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DEPTH_SIZE, 24)
        err = sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
        if err != 0:
            print(sdl2.SDL_GetError())

        self.window = window = sdl2.SDL_CreateWindow(b"sketchz",
            sdl2.SDL_WINDOWPOS_UNDEFINED,
            sdl2.SDL_WINDOWPOS_UNDEFINED, sx, sy,
            sdl2.SDL_WINDOW_OPENGL)

        if not window:
            print(sdl2.SDL_GetError())
            exit()

        self._context = sdl2.SDL_GL_CreateContext(window)
        self.ctx = zengl.context()

        self.img = self.ctx.image((sx, sy), "rgba32float")
        self.img.clear_value = (1, 1, 1, 1)
        self.imgh = self.ctx.image((sx, sy), "rgba32float")     # 很好，同时输出法线和未正规化的深度
        self.imgs = self.ctx.image((sx, sy), "depth32float")


        self.render2d = Render2d()
        #self.temp = load_texture(self.ctx, "cmyk.png")     # 测试
        self.render2d.u(self)

        import cairo
        self.cairo = cairo

        self.cs = self.cairo.ImageSurface(self.cairo.FORMAT_ARGB32, sx, sy)
        self.cctx = self.cairo.Context(self.cs)
        self.cctx.save()

        self.img2 = self.ctx.image((sx, sy), "rgba32float")
        self.renderafter = RenderAfter()
        self.renderafter.u(self)



    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == sdl2.SDL_MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e.motion)
        elif e.type == sdl2.SDL_KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEWHEEL :
            for callback in self.wheel_callback:
                r = callback(self, e.wheel)
                if r == True:
                    break


    def render(self):
        self.ctx.new_frame()

        self.img.clear()
        self.imgs.clear()
        self.imgh.clear()
        self.img2.clear()

        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光


        # ~ self.imgh.blit()



        """
        cctx.move_to(0, 0)
        cctx.line_to(200, 400)
        cctx.line_to(600, 200)
        cctx.set_line_width(3)
        r, g, b, a= (0, 255, 0, 255)
        cctx.set_source_rgba(b / 255, g / 255, r / 255, a / 255)
        cctx.stroke()

        from math import pi


        cctx.arc(500, 400, 200, pi / 2, 0)
        cctx.close_path()

        cctx.fill()
        # ~ cctx.stroke()
        """

        cctx = self.cctx
        cctx.restore()
        cctx.save()

        cctx.rectangle(100, 100, 300, 300)
        cctx.rectangle(200, 200, 100, 100)
        r, g, b, a= (128, 128, 255, 255)
        cctx.set_source_rgba(b / 255, g / 255, r / 255, a / 255)
        cctx.set_fill_rule(self.cairo.FILL_RULE_EVEN_ODD)
        cctx.fill()
        # ~ cctx.new_path()

        cctx.rectangle(350, 100, 300, 300)
        cctx.rectangle(450, 200, 100, 100)
        # ~ r, g, b, a= (255, 128, 255, 255)
        # ~ cctx.set_source_rgba(b / 255, g / 255, r / 255, a / 255)

        lg = self.cairo.LinearGradient(350, 100, 300, 300)
        r, g, b, a = (255, 0, 0, 255)
        lg.add_color_stop_rgba(0, b / 255, g / 255, r / 255, a / 255)
        r, g, b, a = (0, 255, 0, 255)
        lg.add_color_stop_rgba(1, b / 255, g / 255, r / 255, a / 255)
        cctx.set_source(lg)
        cctx.fill()
        '''
        pattern = self.cairo.MeshPattern()
        pattern.begin_patch()
        # ~ pattern.move_to(0, 0)
        pattern.move_to(350, 100)
        pattern.curve_to(30, -30, 60, 30, 100, 0)
        pattern.curve_to(60, 30, 130, 60, 100, 100)
        pattern.curve_to(60, 70, 30, 130, 0, 100)
        pattern.curve_to(30, 70, -30, 30, 0, 0)
        pattern.set_corner_color_rgba(0, 1, 0, 0, 1)
        pattern.set_corner_color_rgba(1, 0, 1, 0, 1)
        pattern.set_corner_color_rgba(2, 0, 0, 1, 1)
        pattern.set_corner_color_rgba(3, 1, 1, 0, 1)
        pattern.end_patch()
        pattern.set_extend(self.cairo.EXTEND_REPEAT)
        cctx.set_source(pattern)
        '''



        # ~ buf = self.cs.get_data()     # 这是一个memoryview
        # ~ self.render2d.texture.write(buf)
        #cs.finish() # 此时内存被释放，无法再获取



        # ~ self.render2d.draw()

        self.renderafter.draw()

        self.img2.blit()

        self.ctx.end_frame()

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt)

    def mainloop(self):
        running = True
        e = sdl2.SDL_Event()
        while running:
            while sdl2.SDL_PollEvent(ctypes.byref(e)) != 0:
                if e.type == sdl2.SDL_QUIT:
                    running = False
                self.dispatch(e)

            self.update(0.02)
            self.render()

            sdl2.SDL_GL_SwapWindow(self.window)
            sdl2.SDL_Delay(20)


        sdl2.SDL_DestroyWindow(self.window)
        sdl2.SDL_Quit()


class WindowAfter:
    def __init__(self, size=(800, 600)):
        sx, sy = self.size = size
        self.size_half = (size[0] // 2, size[1] // 2)
        self.camera = Camera(U3(0, 0, 0), xy=sx/sy)   # 默认摄像机
        self.draw3d_list = []

        self.key_down_callback = []
        self.key_up_callback = []
        self.mouse_down_callback = []
        self.mouse_up_callback = []
        self.mouse_move_callback = []
        self.wheel_callback = []
        self.update_callback = []


        # ~ sdl2.SDL_SetHint(sdl2.SDL_HINT_OPENGL_ES_DRIVER, b"0")
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MAJOR_VERSION, 4)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_MINOR_VERSION, 3)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_CONTEXT_PROFILE_MASK, sdl2.SDL_GL_CONTEXT_PROFILE_CORE)

        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DOUBLEBUFFER, 1)
        sdl2.SDL_GL_SetAttribute(sdl2.SDL_GL_DEPTH_SIZE, 24)
        err = sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO)
        if err != 0:
            print(sdl2.SDL_GetError())

        self.window = window = sdl2.SDL_CreateWindow(b"sketchz",
            sdl2.SDL_WINDOWPOS_UNDEFINED,
            sdl2.SDL_WINDOWPOS_UNDEFINED, sx, sy,
            sdl2.SDL_WINDOW_OPENGL)

        if not window:
            print(sdl2.SDL_GetError())
            exit()

        self._context = sdl2.SDL_GL_CreateContext(window)
        self.ctx = zengl.context()

        self.img = self.ctx.image((sx, sy), "rgba32float")
        self.img.clear_value = (1, 1, 1, 1)
        self.imgh = self.ctx.image((sx, sy), "rgba32float")     # 很好，同时输出法线和未正规化的深度
        self.imgs = self.ctx.image((sx, sy), "depth32float")


        self.render2d = Render2d()
        #self.temp = load_texture(self.ctx, "cmyk.png")     # 测试
        self.render2d.u(self)


        self.img2 = self.ctx.image((sx, sy), "rgba32float")
        self.renderafter = RenderAfter()
        self.renderafter.u(self)



    def o(self, p):
        x, y = p
        a, b = self.size_half
        return V2(x * 100 + a, y * -100 + b)


    def o_inv(self, p):
        x, y = p
        a, b = self.size_half
        return V2((x - a) / 100, (b - y) / 100)


    def os(self, x):
        return x * 100


    def os_inv(self, x):
        return x / 100


    def __rrshift__(self, obj):
        if isinstance(obj, Drawable):
            self.draw3d_list.append(obj)
        elif isinstance(obj, Control):
            if hasattr(obj, "draw"):
                self.draw2d_list.append(obj)
            if hasattr(obj, "reg_event"):     # 并不能确定要注册多少种事件，并且事件可以临时删除
                obj.reg_event(self)
        elif isinstance(obj, Camera):
            self.camera = obj
        else:
            raise Exception(f" >> window fail, {obj}")

    def dispatch(self, e):
        if e.type == sdl2.SDL_MOUSEBUTTONDOWN:
            for callback in self.mouse_down_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEBUTTONUP:
            for callback in self.mouse_up_callback:
                r = callback(self, e.button)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEMOTION:
            for callback in self.mouse_move_callback:
                callback(self, e.motion)
        elif e.type == sdl2.SDL_KEYDOWN:
            for callback in self.key_down_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_KEYUP:
            for callback in self.key_up_callback:
                r = callback(self, e.key)
                if r == True:
                    break
        elif e.type == sdl2.SDL_MOUSEWHEEL :
            for callback in self.wheel_callback:
                r = callback(self, e.wheel)
                if r == True:
                    break


    def render(self):
        self.ctx.new_frame()

        self.img.clear()
        self.imgs.clear()
        self.imgh.clear()
        self.img2.clear()

        for obj in self.draw3d_list:
            obj.draw(self.camera)       # 暂时不考虑灯光



        self.renderafter.draw()

        self.img2.blit()

        self.ctx.end_frame()

    def update(self, dt):
        for callback in self.update_callback:
            callback(self, dt)

    def mainloop(self):
        running = True
        e = sdl2.SDL_Event()
        while running:
            while sdl2.SDL_PollEvent(ctypes.byref(e)) != 0:
                if e.type == sdl2.SDL_QUIT:
                    running = False
                self.dispatch(e)

            self.update(0.02)
            self.render()

            sdl2.SDL_GL_SwapWindow(self.window)
            sdl2.SDL_Delay(20)


        sdl2.SDL_DestroyWindow(self.window)
        sdl2.SDL_Quit()



prog_render_after = {
    "vertex_shader": '''
#version 330 core

layout (location = 0) in vec3 in_position;
layout (location = 1) in vec2 in_texcoord_0;

layout (std140) uniform Common {
    mat4 all_mat;
    vec2 uResolution;
    float fovy;
    float n1;
};

out vec2 uv;

void main() {
    gl_Position = all_mat * vec4(in_position, 1.0);
    uv = vec2(in_texcoord_0.x, 1 - in_texcoord_0.y);
}
 ''',
    "fragment_shader": '''
#version 330 core
layout (location = 0) out vec4 fragColor;

uniform sampler2D texture0;
uniform sampler2D texture1;

layout (std140) uniform Common {
    mat4 all_mat;
    vec2 uResolution;
    float fovy;
    float n1;
};

in vec2 uv;

vec4 alphaBlend(vec4 top, vec4 bottom)
{
    vec3 color = (top.rgb * top.a) + (bottom.rgb * (1 - top.a));
    float alpha = top.a + bottom.a * (1 - top.a);

    return vec4(color, alpha);
}

vec4 Frag(sampler2D tMap, sampler2D normal_texture, vec2 uv)
{
    float _Scale = 1;
    vec4 _Color = vec4(0.0, 0.0, 0.0, 1.0);
    //float _DepthThreshold = 1.5;
    //float _DepthThreshold = 2;
    float _DepthThreshold = 0.01;

    float _DepthNormalThreshold = 3;
    float _DepthNormalThresholdScale = 5;

    float _NormalThreshold = 0.1;

    float halfScaleFloor = floor(_Scale * 0.5);
    float halfScaleCeil = ceil(_Scale * 0.5);

    vec2 TexelSize = vec2(1 / uResolution.x, 1 / uResolution.y);

    vec2 bottomLeftUV = uv - TexelSize * halfScaleFloor;
    vec2 topRightUV = uv + TexelSize * halfScaleCeil;
    vec2 bottomRightUV = uv + vec2(TexelSize.x * halfScaleCeil, -TexelSize.y * halfScaleFloor);
    vec2 topLeftUV = uv + vec2(-TexelSize.x * halfScaleFloor, TexelSize.y * halfScaleCeil);

    vec4 p_i = texture2D(normal_texture, uv);
    vec4 p0 = texture2D(normal_texture, bottomLeftUV);
    vec4 p1 = texture2D(normal_texture, topRightUV);
    vec4 p2 = texture2D(normal_texture, bottomRightUV);
    vec4 p3 = texture2D(normal_texture, topLeftUV);
    vec3 normalp = normalize(p_i.rgb);
    vec3 normal0 = normalize(p0.rgb);
    vec3 normal1 = normalize(p1.rgb);
    vec3 normal2 = normalize(p2.rgb);
    vec3 normal3 = normalize(p3.rgb);
    float depthp = length(p_i.rgb);
    float depth0 = length(p0.rgb);
    float depth1 = length(p1.rgb);
    float depth2 = length(p2.rgb);
    float depth3 = length(p3.rgb);
    vec3 viewNormal = normalp;

    vec2 temp5 = uv * 2 - 1;
    temp5 = vec2(temp5.x * uResolution.x / uResolution.y, temp5.y);
    temp5 = temp5 * tan(fovy / 2);
    vec3 keep_xyz = normalize(vec3(temp5, -1));
    //float NdotV = 1 - acos(dot(viewNormal, -keep_xyz));  // angle of plane and eyelight
    float myth = length(viewNormal) >1.6 ? 0 : acos(dot(viewNormal, -keep_xyz));  // angle of plane normal and eyelight
    myth = clamp(myth, 0, 1.5707);
    // if empty there return 0

    //float normalThreshold01 = clamp((NdotV - _DepthNormalThreshold) / (1 - _DepthNormalThreshold), 0.0, 1.0);
    //float normalThreshold = normalThreshold01 * _DepthNormalThresholdScale + 1;

    float normalThreshold = tan(myth) + 0.01;
    //float depthThreshold = _DepthThreshold * depth0 * normalThreshold * 0.1 + 0.01;
    //float depthThreshold = _DepthThreshold * depth0 * normalThreshold /3;
    float depthThreshold = _DepthThreshold / cos(myth) + depth0 * tan(max(myth, 0.7)) * TexelSize.x * 2 ;

    float depthFiniteDifference0 = depth1 - depth0;
    float depthFiniteDifference1 = depth3 - depth2;

    float edgeDepth = sqrt(pow(depthFiniteDifference0, 2) + pow(depthFiniteDifference1, 2))   ;
    float edgeDepth2 = edgeDepth > depthThreshold ? 1 : 0;

    vec3 normalFiniteDifference0 = normal1 - normal0;
    vec3 normalFiniteDifference1 = normal3 - normal2;
    float edgeNormal = sqrt(dot(normalFiniteDifference0, normalFiniteDifference0) + dot(normalFiniteDifference1, normalFiniteDifference1));
    edgeNormal = edgeNormal > _NormalThreshold ? 1 : 0;

    float edge = max(edgeDepth2, edgeNormal);
    //edge = edge * 0 + 0.5;
    //edge = edge * 0 + (p_i.w * 2 - 1);
    //edge = edge * 0 + edgeNormal;
    //edge = edge * 0 +edgeDepth *10;
    //edge = edge * 0 +depthThreshold * 10;
    //edge = edge * 0 +(edgeDepth - depthThreshold) * 10;
    //edge = edge * 0 + myth / 1.5;
    vec4 edgeColor = vec4(_Color.rgb, _Color.a * edge);
    //vec4 edgeColor = vec4(_Color.rgb, _Color.a * edge) * 0 + vec4(1, 0, 0, 1);
    vec4 color = texture2D(tMap, uv);
    //color.xyz = color.xyz * 0.3 + vec3(0.7, 0.7, 0.7);
    //vec4 help = vec4(edgeDepth, edgeDepth, edgeDepth, 1);
    //return edgeColor + color * 0;
    //return edgeColor * 0 + color * 0 + vec4(viewNormal , 1);
    //return edgeColor * 0 + color * 0 + vec4(keep_xyz * 0.5 + 0.5, 1);
    //return edgeColor * 0 + color * 0 + vec4(uv.x, uv.y, 0, 1);

    //return alphaBlend(edgeColor, color) * 0 + help;
    return alphaBlend(edgeColor, color);
}
void main() {
    vec4 side = Frag(texture0, texture1,  uv);
    fragColor = side;
}


            '''
}

class RenderAfter(Drawable):
    def __init__(self):
        self.pipeline  = None
        self.uniform_buffer = None
        self.texture = None


    def u(self, window):
        assert hasattr(window, "render")
        assert hasattr(window, "update")
        assert hasattr(window, "mainloop")

        try:
            self.uniform_buffer = window.ctx.buffer(size=80, dynamic=True)
        except:
            self.uniform_buffer = window.ctx.buffer(size=80, uniform=True)
        self.texture = window.img
        self.texture2 = window.imgh

        arr = create_screen_quad()
        buf = window.ctx.buffer(arr.tobytes())
        self.pipeline = window.ctx.pipeline(
            vertex_shader = prog_render_after["vertex_shader"],
            fragment_shader = prog_render_after["fragment_shader"],
            framebuffer = [window.img2, window.imgs],
            layout=[
                {'name': 'Common', 'binding': 0},
                {'name': 'texture0', 'binding': 0},
                {'name': 'texture1', 'binding': 1},
            ],
            resources=[
                {
                    'type': 'uniform_buffer',
                    'binding': 0,
                    'buffer': self.uniform_buffer,
                },
                {
                    'type': 'sampler',
                    'binding': 0,
                    'image': self.texture,
                },
                {
                    'type': 'sampler',
                    'binding': 1,
                    'image': self.texture2,
                },
            ],
            vertex_buffers = zengl.bind(buf, "3f 2f", 0, 1),
            vertex_count = buf.size // zengl.calcsize('3f 2f'),
            blend = {
                'enable': True,
                'src_color': 'src_alpha',
                'dst_color': 'one_minus_src_alpha',
                'src_alpha': 'one',
                'dst_alpha': 'one_minus_src_alpha',
            },
        )

        v = Quaternion().matrix44 *  Matrix44.from_translation(-U3(0, -10, 0))
        p = Matrix44.orthogonal_projection(-1, 1, -1, 1, 0, window.size[0])
        am = (p * v).astype("f4")
        self.uniform_buffer.write(struct.pack('64s4f', am.tobytes(), window.size[0], window.size[1], 60 / 180 * pi, 0))

    def draw(self):
        self.pipeline.render()



